<?php
/**
 * Junction_MappingDom
 * 
 * @author Patrick J. Mizer <patrick.mizer@gmail.com>
 */

using("Junction_Class"); 
using("Junction_Table");
using("Junction_Column");
 
 
class Junction_MappingDom
{
	/**
	 * Class element tag.
	 * 
	 * @var string
	 */
	private static $_CLASS_ELEMENT = 'class';
	
	/**
	 * Class name attribute label.
	 * 
	 * @var string
	 */	
	private static $_CLASS_NAME_ATTRIBUTE = 'name';

	/**
	 * Class database attribute label.
	 * 
	 * @var string
	 */	
	private static $_CLASS_DATABASE_ATTRIBUTE = 'database';
	
	/**
	 * Class table attribute label.
	 * 
	 * @var string
	 */	
	private static $_CLASS_TABLE_ATTRIBUTE = 'table';
	
	/**
	 * ID element tag.
	 * 
	 * @var string
	 */	
	private static $_ID_ELEMENT = 'id';
	
	/**
	 * ID name attribute label.
	 * 
	 * @var string
	 */	
	private static $_ID_NAME_ATTRIBUTE = 'name';
	
	/**
	 * ID column attribute label.
	 * 
	 * @var string
	 */	
	private static $_ID_COLUMN_ATTRIBUTE = 'column';
	
	/**
	 * Property element tag.
	 * 
	 * @var string
	 */	
	private static $_PROPERTY_ELEMENT = 'property';
	
	/**
	 * Property name attribute label.
	 * 
	 * @var string
	 */	
	private static $_PROPERTY_NAME_ATTRIBUTE = 'name';
	
	/**
	 * Property column attribute label.
	 * 
	 * @var string
	 */	
	private static $_PROPERTY_COLUMN_ATTRIBUTE = 'column';
	
	/**
	 * MappingDom reference hash.  Facilitates singleton factory method
	 * getMappingDom().
	 * 
	 * @var array string => MappingDom
	 */
	private static $_ref;
	
	/**
	 * DOM Document.
	 * 
	 * @var DOMDocument
	 */
	private $_domDocument;
	
	/**
	 * Junction_MappingDom Contructor.
	 * 
	 * @throws Exception file not found
	 * @param string filename.
	 */
	private function __construct($file)
	{
		$this->_domDocument = new DOMDocument();

		if(!@$this->_domDocument->load($file)){
			throw new Exception('Could not load XML file.');
		}
	}
	
	/**
	 * Factory method to build DOM Parser.  Since we 
	 * don't ever want to rebuild the DOM on any 1 request
	 * we implement this factory method as a singelton.
	 * 
	 * @throws Exception file not found
	 * @param string filename
	 * @return Junction_MappingDom
	 */
	public function getMappingDom($file)
	{
		if(!isset(Junction_MappingDom::$_ref[$file])){
			Junction_MappingDom::$_ref[$file] = new Junction_MappingDom($file);
		}
		
		return Junction_MappingDom::$_ref[$file];
	}
	
	/**
	 * Gets Junction_Class from DOM.
	 * 
	 * <p>This method lazy loads the classes.  If n (n >= 1) calls to getClass 
	 * are made in one request the DOM tree will be traversed at max once.  
	 * 
	 * @throws Exception class not found
	 * @param string class name
	 * @return Junction_Class
	 */
	public function getClass($className)
	{
		if(isset($this->_classes[$className]))
			return $this->_classes[$className];
		
		$classes = $this->_domDocument->getElementsByTagName(
			Junction_MappingDom::$_CLASS_ELEMENT
		);
		
		foreach($classes as $class){
			if((string)$className == (string)$class->getAttribute(
					Junction_MappingDom::$_CLASS_NAME_ATTRIBUTE)){
				$this->_classes[$className] = $this->_prepareClassFromDom($class);
				return $this->_classes[$className];
			}
		}
		throw new Exception('Class '.$className.' not found!');
	}
	
	/**
	 * Convenience method which given a class DomElement
	 * converts that element into Junction_Class instance.
	 * 
	 * @throws Exception malformed XML
	 * @param DomElement class
	 * @return Junction_Class
	 */
	private function _prepareClassFromDom(DomElement $classElement)
	{
		$className = $classElement->getAttribute(
			Junction_MappingDom::$_CLASS_NAME_ATTRIBUTE
		);
		
		$classTable = $classElement->getAttribute(
			Junction_MappingDom::$_CLASS_TABLE_ATTRIBUTE
		);
		
		$classId = $classElement->getElementsByTagName(
			Junction_MappingDom::$_ID_ELEMENT
		);
		
		$classPkName = $classId->item(0)->getAttribute(
			Junction_MappingDom::$_ID_NAME_ATTRIBUTE
		);
		
		$classPkCol = $classId->item(0)->getAttribute(
			Junction_MappingDom::$_ID_COLUMN_ATTRIBUTE
		);
		
		$classProperties = $classElement->getElementsByTagName(
			Junction_MappingDom::$_PROPERTY_ELEMENT
		);	
		
		$table = new Junction_Table($classTable);
		$class = new Junction_Class($className);
		
		$class->setPrimaryKey(
			$classPkName,
			new Junction_Column($table, $classPkCol)
		);
		
		foreach($classProperties as $classProperty){
			$propertyName = $classProperty->getAttribute(
				Junction_MappingDom::$_PROPERTY_NAME_ATTRIBUTE
			);
			
			$propertyColumn = $classProperty->getAttribute(
				Junction_MappingDom::$_PROPERTY_COLUMN_ATTRIBUTE
			);
			
			$column = new Junction_Column($table, $propertyColumn);
			
			$class->setAttribute($propertyName, $column);
		}
		
		return $class;
	}
}
?>